#include  <linux/kernel.h>         /* We're doing kernel work */
#include  <linux/module.h>         /* Specifically, a module */
#include  <linux/fs.h>
#include  <asm/uaccess.h>          /* for get_user and put_user */
#include  <linux/proc_fs.h>
#include  <linux/random.h>
#include  <linux/string.h>
#include  <linux/uaccess.h>
#include  <linux/ioctl.h>

#define SUCCESS 0
#define DEVICE_NAME "probabilidad"
#define DEVICE_FILE_NAME "/dev/probabilidad"
#define PROC_FILE_NAME  "probabilidad"
#define PROC_BUFF_MAX_SIZE  20

#define MAJOR_NUM 100
//Get the message of the device driver
#define IOCTL_GET_MSG _IOR(MAJOR_NUM, 1, char *)

static int __init init_char_module(void);
static void __exit cleanup_char_module(void);

//se informa al kernel las funciones de inicio y finalizacion
module_init(init_char_module);
module_exit(cleanup_char_module);

//contador para evitar accesos concurrentes a nuestro device
static int Device_Open = 0;

//struct para manejar el archivo 
static struct proc_dir_entry *proc_file;

//registro de la cantidad de lecturas
static int n_lecturas = 0;

//buffer para devolver n_lecturas en forma de string
static char proc_buffer[PROC_BUFF_MAX_SIZE];

//tamaño del buffer para devolver la cantidad de lecturas sobre /dev/probabilidad
static unsigned long proc_buffer_size = 0;

//funcion invocada cuando se realiza un open del device
//la dejamos para un correcto funcionamiento del modulo
static int device_open(struct inode *inode, struct file *file)
{
    //si alguien ya me abrio, entonces no doy bola al resto
    if (Device_Open)
    {
        return -EBUSY;
    }
    
    Device_Open++;
    
    try_module_get(THIS_MODULE);
    return SUCCESS;
}

//funcion invocada cuando se hace un close del device
//la dejamos por cuestiones de correcto funcionamiento
static int device_release(struct inode *inode, struct file *file)
{
    Device_Open--;
    module_put(THIS_MODULE);
    return SUCCESS;
}

void reverse(char s[])
{
    int i, j;
    char c;

    for (i = 0, j = strlen(s)-1; i<j; i++, j--) {
        c = s[i];
        s[i] = s[j];
        s[j] = c;
    }
}


void itoa(unsigned long n, char s[])
{
    int i=0;

    do {   
        s[i++] = n % 10 + '0';
    } while ((n /= 10) > 0);

    s[i] = '\0';

    //actualizo el tamaño del buffer
    proc_buffer_size = i;

    reverse(s);
} 

//Cuando leemos /proc/probabilidad se invoca esta funcion, que mediante itoa
//guarda en el buffer la representacion en chars de la cantidad de veces que se 
//ha leido /dev/probabilidad
int get_n_lecturas(char *buffer,
               char **buffer_location,
               off_t offset, int buffer_length, int *eof, void *data)
{
    int ret;

    if (offset > 0) {
          /* we have finished to read, return 0 */
          ret = 0;
    } else {
          /* fill the buffer, return the buffer size */
          itoa(n_lecturas, proc_buffer);
          memcpy(buffer, proc_buffer, proc_buffer_size);
          ret = proc_buffer_size;
    }
    return ret;
}


int set_seed(struct file *filp, const char __user *buff, unsigned long len, void *data){
    unsigned int n = buff[0] - '0';
    
    srandom32(n);
    
	return len;
}

//esta funcion se invoca cada vez que se intenta leer
static ssize_t device_read(struct file *file,      /* see include/linux/fs.h   */
                             char __user * buffer,         /* buffer to be filled with data*/
                             size_t length,        /* length of the buffer */
                             loff_t * offset)
{

    if(proc_file==NULL)
    {
        buffer[0] = ':';
    }
    else
    {
        n_lecturas++;
        
        buffer[0] = ((char) (random32()%26)) + 'A';
    }
    //informamos que hemos leido solo un caracter
    return 1;
}

//esta estructura va a mantener los punteros a las funciones que van a ser llamadas 
//cuando nuestro device file sea leido, escrito, etc. NULL indica que no la funcion
//no ha sido implementada. En nuestro caso solo nos interesa la lectura.
struct file_operations Fops = {
        .read = device_read,
        .write = NULL,
        .ioctl = NULL,
        .open = device_open,
        .release = device_release,
};


//Inicializar el modulo. Registramos el chardevice y creamos un archivo en /proc
static int __init init_char_module()
{
    int ret_val;
    //registramos el device file
    ret_val = register_chrdev(MAJOR_NUM, DEVICE_NAME, &Fops);

    if (ret_val < 0) {
        printk(KERN_ALERT "%s failed with %d\n",
             "Sorry, registering the character device ", ret_val);
        return ret_val;
    }
    
    //creamos la entrada en /proc para leer lecturas y setear el seed
	proc_file = create_proc_entry(PROC_FILE_NAME, 0644, NULL);
    //indicamos las funciones de escritura y lectura
	proc_file->write_proc = set_seed;
	proc_file->read_proc = get_n_lecturas;

    return 0;
}

//Funcion que finaliza el modulo, limpiando y desregistrando el archivo en /proc y el devicefile
static void __exit cleanup_char_module()
{
    //desregistramos el device    
    unregister_chrdev(MAJOR_NUM, DEVICE_NAME);
    //borramos la entrada en /proc
    remove_proc_entry(PROC_FILE_NAME, NULL);
}

